<!DOCTYPE html>
<html>
  <head><meta name="generator" content="Hexo 3.9.0">
<meta name="google-site-verification" content="fQ_tfBgNjE9NQcpKnGAkWapHoKuimF5lVuNuqpPXar0">
    <meta charset="utf-8">
    
    <title>Websocket-php学习笔记 | Xiao Leung&#39;s Blog</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    
    
      <link rel="icon" href="/favicon.png">
    

    <link rel="stylesheet" href="/css/style.css">

    <link rel="stylesheet" href="/js/google-code-prettify/tomorrow-night-eighties.min.css">

  </head>

  <body>
<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"log":false,"pluginJsPath":"lib/","pluginModelPath":"assets/","pluginRootPath":"live2dw/","tagMode":false});</script></body></html>
<header>

	<a id="logo" href="/" title="Xiao Leung&#39;s Blog">
	<img src="/favicon.png" alt="Xiao Leung&#39;s Blog"></a>
	
	
		<!--搜索栏-->
		<i class="js-toggle-search iconfont icon-search"></i>


<form class="js-search search-form search-form--modal" method="get" action="http://gushi.li" role="search">
	<div class="search-form__inner">
		<div>
			<i class="iconfont icon-search"></i>
			<input class="text-input" placeholder="Enter Key..." type="search">
		</div>
	</div>
</form>
	

	
		<!--侧边导航栏-->
		<a id="nav-toggle" href="#"><span></span></a>

<nav>
	<div class="menu-top-container">
		<ul id="menu-top" class="menu">
			
				
				<li class="current-menu-item">
					<a href="https://www.plasf.cn/2019/08/01/HelloWorld/" target="_blank">AboutMe</a>
				</li>
			
				
				<li class="current-menu-item">
					<a href="https://www.plasf.cn/HXCTF/" target="_blank">HXCTF</a>
				</li>
			
		</ul>
	</div>
</nav>
	

</header>

<div class="m-header ">
	<section id="hero1" class="hero">
		<div class="inner">
		</div>
	</section>
	
		<figure class="top-image" data-enable=true></figure>
	
</div>

<!--文章列表-->
<div class="wrapper">
  
    <!--文章-->
<article>
	
  
    <h1 class="post-title" itemprop="name">
      Websocket-php学习笔记
    </h1>
  

	<div class='post-body mb'>
		<h1 id="Websocket-php学习笔记"><a href="#Websocket-php学习笔记" class="headerlink" title="Websocket-php学习笔记"></a>Websocket-php学习笔记</h1><h2 id="创建一个套接字"><a href="#创建一个套接字" class="headerlink" title="创建一个套接字"></a>创建一个套接字</h2><p>​        <code>socket_create</code>创建并返回一个套接字，也称作一个通讯节点。一个典型的网络连接由 2 个套接字构成，一个运行在客户端，另一个运行在服务器端。</p>
<pre><code class="php">resource socket_create ( int $domain , int $type , int $protocol )</code></pre>
<ul>
<li><code>@parameter domain</code>参数指定哪个协议用在当前套接字上</li>
</ul>
<table>
<thead>
<tr>
<th>Domain</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td><strong><code>AF_INET</code></strong></td>
<td>IPv4 网络协议。TCP 和 UDP 都可使用此协议。</td>
</tr>
<tr>
<td><strong><code>AF_INET6</code></strong></td>
<td>IPv6 网络协议。TCP 和 UDP 都可使用此协议。</td>
</tr>
<tr>
<td><strong><code>AF_UNIX</code></strong></td>
<td>本地通讯协议。具有高性能和低成本的 IPC（进程间通讯）。</td>
</tr>
</tbody></table>
<ul>
<li><code>@parameter type</code>参数用于选择套接字使用的类型</li>
</ul>
<table>
<thead>
<tr>
<th>类型</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td><strong><code>SOCK_STREAM</code></strong></td>
<td>提供一个顺序化的、可靠的、全双工的、基于连接的字节流。支持数据传送流量控制机制。TCP 协议即基于这种流式套接字。</td>
</tr>
<tr>
<td><strong><code>SOCK_DGRAM</code></strong></td>
<td>提供数据报文的支持。(无连接，不可靠、固定最大长度).UDP协议即基于这种数据报文套接字。</td>
</tr>
<tr>
<td><strong><code>SOCK_SEQPACKET</code></strong></td>
<td>提供一个顺序化的、可靠的、全双工的、面向连接的、固定最大长度的数据通信；数据端通过接收每一个数据段来读取整个数据包。</td>
</tr>
<tr>
<td><strong><code>SOCK_RAW</code></strong></td>
<td>提供读取原始的网络协议。这种特殊的套接字可用于手工构建任意类型的协议。一般使用这个套接字来实现 ICMP 请求（例如 ping）。</td>
</tr>
<tr>
<td><strong><code>SOCK_RDM</code></strong></td>
<td>提供一个可靠的数据层，但不保证到达顺序。一般的操作系统都未实现此功能。</td>
</tr>
</tbody></table>
<ul>
<li><code>@parameter protocol</code>是设置指定 <code>domain</code> 套接字下的具体协议。这个值可以使用 <a href="http://php.net/manual/zh/function.getprotobyname.php" target="_blank" rel="noopener">getprotobyname()</a> 函数进行读取。如果所需的协议是 TCP 或 UDP，可以直接使用常量 <code>SOL_TCP</code> 和 <code>SOL_UDP</code> 。</li>
</ul>
<table>
<thead>
<tr>
<th>名称</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td>icmp</td>
<td>Internet Control Message Protocol 主要用于网关和主机报告错误的数据通信。例如“ping”命令（在目前大部分的操作系统中）就是使用 ICMP 协议实现的。</td>
</tr>
<tr>
<td>udp</td>
<td>User Datagram Protocol 是一个无连接的、不可靠的、具有固定最大长度的报文协议。由于这些特性，UDP 协议拥有最小的协议开销。</td>
</tr>
<tr>
<td>tcp</td>
<td>Transmission Control Protocol 是一个可靠的、基于连接的、面向数据流的全双工协议。TCP  能够保障所有的数据包是按照其发送顺序而接收的。如果任意数据包在通讯时丢失，TCP  将自动重发数据包直到目标主机应答已接收。因为可靠性和性能的原因，TCP 在数据传输层使用 8bit 字节边界。因此，TCP  应用程序必须允许传送部分报文的可能。</td>
</tr>
</tbody></table>
<ul>
<li><code>返回值</code>socket_create() 正确时返回一个套接字，失败时返回 <code>FALSE</code>。要读取错误代码，可以调用 <a href="http://php.net/manual/zh/function.socket-last-error.php" target="_blank" rel="noopener">socket_last_error()</a>。这个错误代码可以通过 <a href="http://php.net/manual/zh/function.socket-strerror.php" target="_blank" rel="noopener">socket_strerror()</a> 读取文字的错误说明。</li>
</ul>
<hr>
<p>  <code>socket_bind</code>给套接字绑定名字,<strong>注意：</strong>绑定 <code>address</code> 到 <code>socket</code>。   该操作必须是在使用 <a href="https://www.php.net/manual/zh/function.socket-connect.php" target="_blank" rel="noopener">socket_connect()</a> 或者 <a href="https://www.php.net/manual/zh/function.socket-listen.php" target="_blank" rel="noopener">socket_listen()</a>   建立一个连接之前。  </p>
<pre><code class="php"> socket_bind ( resource $socket , string $address [, int $port = 0 ] ) : bool</code></pre>
<ul>
<li><p><code>@parameter socket</code>用 <a href="https://www.php.net/manual/zh/function.socket-create.php" target="_blank" rel="noopener">socket_create()</a> 创建的一个有效的套接字资源。      </p>
</li>
<li><p><code>@parameter address</code></p>
<ul>
<li>如果套接字是 <strong><code>AF_INET</code></strong> 族，那么       <code>address</code> 必须是一个四点分法的 IP 地址（例如 <em>127.0.0.1</em> ）。</li>
<li>如果套接字是 <strong><code>AF_UNIX</code></strong> 族，那么 <code>address</code> 是 Unix 套接字一部分（例如 /tmp/my.sock ）</li>
</ul>
</li>
<li><p><code>@parameter port</code>参数 <code>port</code> 仅仅用于 <strong><code>AF_INET</code></strong>       套接字连接的时候，并且指定连接中需要监听的端口号。      </p>
</li>
</ul>
<hr>
<p>  ​    <code>socket_listen</code>监听socket连接</p>
<pre><code class="php"> socket_listen ( resource $socket [, int $backlog = 0 ] ) : bool</code></pre>
<ul>
<li><code>@parameter socket</code>用 <a href="https://www.php.net/manual/zh/function.socket-create.php" target="_blank" rel="noopener">socket_create()</a> 创建的一个有效的套接字资源。</li>
<li><code>@parameter backlog</code>端口号</li>
</ul>
<h2 id="创建套接字实例"><a href="#创建套接字实例" class="headerlink" title="创建套接字实例"></a>创建套接字实例</h2><pre><code class="php">public function service(){
    $tcp = getprotobyname(&quot;tcp&quot;);    //获取tcp协议号码。
    $sock = socket_create(AF_INET, SOCK_STREAM, $tcp);//创建一个套接字
    socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1);
    if($sock &lt; 0)
    {
        throw new Exception(&quot;failed to create socket: &quot;.socket_strerror($sock).&quot;\n&quot;);//获取错误
    }
    socket_bind($sock, $this-&gt;address, $this-&gt;port);//绑定IP和端口
    socket_listen($sock, $this-&gt;port);//监听端口
    echo &quot;listen on $this-&gt;address $this-&gt;port ... \n&quot;;
    $this-&gt;_sockets = $sock;
}</code></pre>
<h2 id="握手处理"><a href="#握手处理" class="headerlink" title="握手处理"></a>握手处理</h2><ul>
<li>我们直接从代码示例看,这个类方法传入了两个参数，一个是socket、一个是请求包，在接收到请求包后会遍历这个请求包，获取中的<code>Sec-WebSocket-Key&#39;</code>,<code>Sec-WebSocket-Key</code> 加上一个特殊字符串  <code>258EAFA5-E914-47DA-95CA-C5AB0DC85B11</code>，然后计算 SHA-1 ，之后进行 BASE-64  编码，将结果做为 <code>Sec-WebSocket-Accept</code> 头的值并且插入在响应包中 返回给客户端。</li>
<li><code>socket_write</code> 将字符串写入socket中</li>
</ul>
<pre><code class="php">socket_write( resource `$socket`   , string `$buffer`   [, int `$length` = 0  ] ) : int</code></pre>
<pre><code class="php">/**
 * 握手处理
 * @param $newClient socket
 * @return int  接收到的信息
 */
public function handshaking($newClient, $line){

    $headers = array();
    $lines = preg_split(&quot;/\r\n/&quot;, $line);
    foreach($lines as $line)
    {
        $line = chop($line);//chop()若为空则移除\n\r空格之类的。
        if(preg_match(&#39;/\A(\S+): (.*)\z/&#39;, $line, $matches))//正则获取header头
        {
            $headers[$matches[1]] = $matches[2];//将header存入数组
        }
    }
    $secKey = $headers[&#39;Sec-WebSocket-Key&#39;];
    $secAccept = base64_encode(pack(&#39;H*&#39;, sha1($secKey . &#39;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&#39;)));
    $upgrade  = &quot;HTTP/1.1 101 Web Socket Protocol Handshake\r\n&quot; .
        &quot;Upgrade: websocket\r\n&quot; .
        &quot;Connection: Upgrade\r\n&quot; .
        &quot;WebSocket-Origin: $this-&gt;address\r\n&quot; .
        &quot;WebSocket-Location: ws://$this-&gt;address:$this-&gt;port/websocket/websocket\r\n&quot;.
        &quot;Sec-WebSocket-Accept:$secAccept\r\n\r\n&quot;;  //构造响应包
    return socket_write($newClient, $upgrade, strlen($upgrade));
}</code></pre>
<h2 id="Websocket的数据处理"><a href="#Websocket的数据处理" class="headerlink" title="Websocket的数据处理"></a>Websocket的数据处理</h2><ul>
<li><p><strong>数据帧格式</strong></p>
<p>​    WebSocket客户端，服务端通信的最小单位是帧（frame）,由一个或多个帧组成一条完整的消息(message)</p>
</li>
</ul>
<p><img src="https://www.mycute.cn/static/umeditor/php/upload/20191205/15755606535758.png" alt="img"></p>
<ul>
<li><strong>数据帧格式详解</strong>：</li>
</ul>
<p><strong>FIN</strong></p>
<p>​        如果是1，表示这是消息(message)的最后一个分片（fragment）,如果是0，表示不是消息（message）的最后一个分片（fragment）</p>
<p><strong>Opcode</strong>: </p>
<p>​            4个比特。</p>
<p><strong>操作码</strong></p>
<p>​        Opcode的值决定了应该如何解析后续的数据载荷（data payload）。如果操作码是不认识的，那么接收端应该断开连接（fail the connection）</p>
<p><strong>Mask</strong>: 1个比特。</p>
<p>​        从客户端向服务端发送数据时，需要对数据进行掩码操作；从服务端向客户端发送数据时，不需要对数据进行掩码操作。<br>如果服务端接收到的数据没有进行过掩码操作，服务端需要断开连接。</p>
<p><strong>Payload length</strong>: 数据载荷的长度。 7位  或  7 + 16 位  或  7 + 64位。</p>
<p>​    假设 Payload length == x</p>
<p>x 为 0-126： 数据长度为x字节.<br>x 为 126 ： 后续2个字节代表一个16位无符号整数<br>x  为 127 ： 后续8个字节代表一个64位无符号整数</p>
<p><strong>掩码算法</strong>：</p>
<p> 首先，假设：</p>
<pre><code>original-octet-i：为原始数据的第i字节。
transformed-octet-i：为转换后的数据的第i字节。
j：为i mod 4的结果。
masking-key-octet-j：为mask key第j字节。

算法描述为： original-octet-i 与 masking-key-octet-j 异或后，得到 transformed-octet-i。</code></pre><ul>
<li>数据帧的解析如下：</li>
</ul>
<pre><code class="php">/**
     * 解析接收数据
     * @param $buffer
     * @return null|string
     */
    public function message($buffer){
        $len = $masks = $data = $decoded = null;
        $len = ord($buffer[1]) &amp; 127;//取出第8-15位
        if ($len === 126)  { // 126&lt;=数据长度&lt;= 65535   Extended payload length 有 占用2个字节
            $masks = substr($buffer, 4, 4);//帧格式 Masking-key 占用4字节
            $data = substr($buffer, 8);//第九个字节（索引 8 ）开始 是数据
        } else if ($len === 127)  { // 65535 &lt;= 数据长度  ；Extended payload length 有 占用8个字节
            $masks = substr($buffer, 10, 4);// 说明 Extended payload length 占用 8个字节 2 + 8 所以从 10开始
            $data = substr($buffer, 14);
        } else  {
            $masks = substr($buffer, 2, 4);
            $data = substr($buffer, 6);
        }
        for ($index = 0; $index &lt; strlen($data); $index++) {
            $decoded .= $data[$index] ^ $masks[$index % 4];//掩码转换为原文
        }
        return $decoded;  
    }
</code></pre>

	</div>
	<div class="meta split">
		
			<span>本文总阅读量 <span id="busuanzi_value_page_pv"></span> 次</span>
		
		<time class="post-date" datetime="2019-12-12T03:34:26.000Z" itemprop="datePublished">2019-12-12</time>
	</div>
</article>

<!--评论-->

	
<div class="ds-thread" data-thread-key="Websocket-php学习笔记" data-title="Websocket-php学习笔记" data-url="http://www.plasf.cn/2019/12/12/Websocket-php学习笔记/"></div>
<script type="text/javascript">

var duoshuoQuery = {short_name:"yumemor"};
	(function() {
		var ds = document.createElement('script');
		ds.type = 'text/javascript';ds.async = true;
		ds.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') + '//static.duoshuo.com/embed.js';
		ds.charset = 'UTF-8';
		(document.getElementsByTagName('head')[0]
		 || document.getElementsByTagName('body')[0]).appendChild(ds);
	})();
</script>


  
</div>


  <svg id="bigTriangleColor" width="100%" height="40" viewBox="0 0 100 102" preserveAspectRatio="none">
    <path d="M0 0 L50 100 L100 0 Z"></path>
  </svg>

  


  <div class="wrapper"></div>





<div class="fat-footer">
	<div class="wrapper">
		<div class="layout layout--center">
			<div class="layout__item palm-mb">
				<div class="media">
					<img class="headimg" src='/assets/blogImg/litten.png' alt='XiaoLeung'>
					<div class="media__body">
						<h4>兵至如归-Xiaoleung&#39;s Blog</h4>
						<p class='site-description'>Don&#39;t forget why we started</p>
					</div>
				</div>
				<div class="author-contact">
					<ul>
						
							
							<li>
				        		<a href="https://github.com/sharpleung" target="_blank">
				        			
				        				<i class="iconfont icon-github"></i>
				        			
				        		</a>
				        	</li>
						
					</ul>
				</div>
			</div>
		</div>
	</div>
</div>

<footer class="footer" role="contentinfo">
	<div class="wrapper wrapper--wide split split--responsive">
<a href="http://beian.miit.gov.cn/">粤ICP备18132442号-1</a><br>
<a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=44011202000643" style="display:inline-block;text-decoration:none;height:20px;line-height:20px;"><img src="http://beian.gov.cn/img/ghs.png" style="float:left;"/><p style="float:left;height:20px;line-height:20px;margin: 0px 0px 0px 5px; color:#939393;">粤公网安备 44011202000643号</p></a><br>

		
			<span>本站总访问量 <span id="busuanzi_value_site_pv"></span> 次, 访客数 <span id="busuanzi_value_site_uv"></span> 人次</span>
		
		<span>Theme by <a href="http://github.com/justpsvm">justpsvm</a>. Powered by <a href="http://hexo.io">Hexo</a></span>
	</div>
</footer>

	<!-－这里导入了 lib.js 里面涵盖了 jQuery 等框架 所以注释掉-->
	<!--<script src="http://lib.sinaapp.com/js/jquery/2.0/jquery.min.js"></script>-->
	<script src="/js/lib.js"></script>
	<script src="/js/google-code-prettify/prettify.js"></script>
	<script src="/js/module.js"></script>
	<script src="/js/script.js"></script>
	
		<script async src="http://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script>
	
	<script type='text/javascript'>
		//代码高亮
		$(document).ready(function(){
	 		$('pre').addClass('prettyprint linenums').attr('style', 'overflow:auto;');
   			prettyPrint();
		});
	</script>
	<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"log":false,"pluginJsPath":"lib/","pluginModelPath":"assets/","pluginRootPath":"live2dw/","tagMode":false});</script><script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"log":false,"pluginJsPath":"lib/","pluginModelPath":"assets/","pluginRootPath":"live2dw/","tagMode":false});</script></body>
</html>

<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
 <script type="text/javascript"> /* 鼠标点击特效 - 7Core.CN */ var a_idx = 0;jQuery(document).ready(function($) {$("body").click(function(e) {var a = new Array("富强", "民主", "文明", "和谐", "自由", "平等", "公正" ,"法治", "爱国", "敬业", "诚信", "友善");var $i = $("<span/>").text(a[a_idx]); a_idx = (a_idx + 1) % a.length;var x = e.pageX,y = e.pageY;$i.css({"z-index": 100000000,"top": y - 20,"left": x,"position": "absolute","font-weight": "bold","color": "#ff6651"});$("body").append($i);$i.animate({"top": y - 180,"opacity": 0},1500,function() {$i.remove();});});}); </script>

